<?php
/*
 * Created on Jul 9, 2008
 *
 * @author Yang Tao our1944@gmail.com
 *
 * @version API version 5
 */

Class FogbugzRestLib {
  private $uri = null;
  private $location = null;
  private $email = null;
  private $password = null;
  private $token = null;

  public function __construct($uri, $token = '', $email = '', $password = '') {
    $this->uri = $uri;
    $this->token = $token;
    $this->email = $email;
    $this->password = $password;
    if(!$token) {
      $this->logon();
    }
  }

  /**
   * Send a get requrest to a server
   * @param Stirng $param
   * @param String $method POST or GET
   */
  private function sendRequest($param, $method = "POST") {
    $opts = array(
        'http' => array(
            'method' => $method,
            'header' => 'Content-type:application/x-www-form-urlencoded',
            'content' => $param,
            ),
        );
    $context = stream_context_create($opts);
    $fp = fopen($this->uri, "r", false, $context);
    if(!$fp) {
      throw new ConnectionFailedException();
    }
    $output = '';
    while (!feof($fp)) {
      $output .= fgets($fp, 128);
    }
    fclose($fp);
    return $output;
  }



  private function stripXML($input) {
    $string = substr($input, strpos($input, "<?xml"));
    return new SimpleXMLElement($string);
  }

  /**
   * Send fogbugz a request and return the response
   * @param string $param
   * @return SimpleXMLElement
   * @see sendRequest
   */
  private function getResponse ($param) {

    if (is_null($this->token)) {
      throw new NotLogOnException();
    }
    $xml = $this->stripXML($this->sendRequest($param));
    $this->isError($xml);
    return $xml;
  }
  /**
   * Throw a FogBugzErrorException if there is an fogbugz error in xml
   * @param SimpleXMLElement $SimpleXML
   */
  private function isError($SimpleXML) {
    if ($SimpleXML->error) {
      $attributes = $SimpleXML->error->attributes();
      throw new FogbugzErrorException((string) $SimpleXML->error, (int) $attributes['code']);
    }
  }

  /**
   * Helper function which converts an array to a parameter that will be passed to a function
   * @param array $param
   * @return string
   */
  public function array2Param($param) {
    $output = null;
    foreach ($param as $key => $value) {
      $output .= "&". $key. "=".urlencode($value);
    }
    return $output;
  }


  /**
   * Logon to the fogbugz system and get the token
   * @return String $token
   */
  public function logon() {
    $param = "cmd=logon&email=" .$this->email ."&password=" .$this->password;
    $xml = $this->stripXML($this->sendRequest($param));
    $this->isError($xml);
    $this->token = $xml->token;
    return $xml->token;
  }
  public function getToken() {
    return $this->token;
  }

  /**
   * Logoff from the fogbugz system
   */
  public function logoff() {
    $param = "cmd=logoff&token=".$this->token;
    try {
      if (is_null($this->token)) {
        throw new NotLogOnException();
      }
      $fp = $this->sendRequest($param);
    } catch (Exception $e) {
      fclose($fp);
      echo "Exception caught: ",  e;
    }
    fclose($fp);
  }

  /**
   * This function send a command and return an SimpleXMLElement object
   * @return SimpleXMLElement
   */
  public function runCmd($cmd, $parameters='') {
    if ($parameters) {
      $parameters = $this->array2Param($parameters);
    }
    $param = "cmd=$cmd$parameters&token=$this->token";
    $xml = $this->getResponse($param);
    return $xml;
  }
}

Class ConnectionFailedException extends Exception {
  public function __toString() {
    return __CLASS__;
  }
}

Class FogbugzErrorException extends Exception {
  public function __construct($messege, $code) {
    parent::__construct($messege, $code);
  }
  public function __toString() {
    return __CLASS__ . "Code [{$this->code}]: {$this->message}";
  }
  public function errorCode() {
    return $this->code;
  }
}

Class NotLogOnException extends Exception {
  public function __toString() {
    return __CLASS__;
  }
}

Class NullOpenedSockException extends Exception {
  public function __toString() {
    return __CLASS__;
  }
}
?>
